home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / js / calCalendarManager.js < prev    next >
Text File  |  2008-03-01  |  39KB  |  981 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Oracle Corporation code.
  15.  *
  16.  * The Initial Developer of the Original Code is Oracle Corporation
  17.  * Portions created by the Initial Developer are Copyright (C) 2005
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Stuart Parmenter <stuart.parmenter@oracle.com>
  22.  *   Matthew Willis <lilmatt@mozilla.com>
  23.  *   Michiel van Leeuwen <mvl@exedo.nl>
  24.  *   Martin Schroeder <mschroeder@mozilla.x-home.org>
  25.  *   Philipp Kewisch <mozilla@kewis.ch>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. const SUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
  42. const LIGHTNING_UID = "{e2fda1a4-762b-4020-b5ad-a41df1933103}";
  43.  
  44. const kStorageServiceContractID = "@mozilla.org/storage/service;1";
  45. const kStorageServiceIID = Components.interfaces.mozIStorageService;
  46.  
  47. const kMozStorageStatementWrapperContractID = "@mozilla.org/storage/statement-wrapper;1";
  48. const kMozStorageStatementWrapperIID = Components.interfaces.mozIStorageStatementWrapper;
  49. var MozStorageStatementWrapper = null;
  50.  
  51. function createStatement (dbconn, sql) {
  52.     var stmt = dbconn.createStatement(sql);
  53.     var wrapper = MozStorageStatementWrapper();
  54.     wrapper.initialize(stmt);
  55.     return wrapper;
  56. }
  57.  
  58. function onCalCalendarManagerLoad() {
  59.     MozStorageStatementWrapper = new Components.Constructor(kMozStorageStatementWrapperContractID, kMozStorageStatementWrapperIID);
  60. }
  61.  
  62. function calCalendarManager() {
  63.     this.wrappedJSObject = this;
  64.     this.setUpStartupObservers();
  65. }
  66.  
  67. var calCalendarManagerClassInfo = {
  68.     getInterfaces: function (count) {
  69.         var ifaces = [
  70.             Components.interfaces.nsISupports,
  71.             Components.interfaces.calICalendarManager,
  72.             Components.interfaces.nsIObserver,
  73.             Components.interfaces.nsIClassInfo
  74.         ];
  75.         count.value = ifaces.length;
  76.         return ifaces;
  77.     },
  78.  
  79.     getHelperForLanguage: function (language) {
  80.         return null;
  81.     },
  82.  
  83.     contractID: "@mozilla.org/calendar/manager;1",
  84.     classDescription: "Calendar Manager",
  85.     classID: Components.ID("{f42585e7-e736-4600-985d-9624c1c51992}"),
  86.     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
  87.     flags: 0
  88. };
  89.  
  90. calCalendarManager.prototype = {
  91.     QueryInterface: function (aIID) {
  92.         return doQueryInterface(this,
  93.                                 calCalendarManager.prototype,
  94.                                 aIID,
  95.                                 null,
  96.                                 calCalendarManagerClassInfo);
  97.     },
  98.  
  99.     get networkCalendarCount() {
  100.         return this.mNetworkCalendarCount;
  101.     },
  102.  
  103.     get readOnlyCalendarCount() {
  104.         return this.mReadonlyCalendarCount;
  105.     },
  106.  
  107.     get calendarCount() {
  108.         return this.mCalendarCount;
  109.     },
  110.     
  111.     setUpStartupObservers: function ccm_setUpStartupObservers() {
  112.         var observerSvc = Components.classes["@mozilla.org/observer-service;1"]
  113.                           .getService(Components.interfaces.nsIObserverService);
  114.  
  115.         observerSvc.addObserver(this, "profile-after-change", false);
  116.         observerSvc.addObserver(this, "profile-before-change", false);
  117.     },
  118.     
  119.     startup: function ccm_startup() {
  120.         this.initDB();
  121.         this.mCache = null;
  122.         this.mCalObservers = null;
  123.         this.mRefreshTimer = null;
  124.         this.setUpPrefObservers();
  125.         this.setUpRefreshTimer();
  126.         this.setupOfflineObservers();
  127.         this.mNetworkCalendarCount = 0;
  128.         this.mReadonlyCalendarCount = 0;
  129.         this.mCalendarCount = 0;
  130.     },
  131.     
  132.     shutdown: function ccm_shutdown() {
  133.         for each (var cal in this.mCache) {
  134.             cal.removeObserver(this.mCalObservers[cal.id]);
  135.         }
  136.  
  137.         this.cleanupPrefObservers();
  138.         this.cleanupOfflineObservers();
  139.  
  140.         var observerSvc = Components.classes["@mozilla.org/observer-service;1"]
  141.                           .getService(Components.interfaces.nsIObserverService);
  142.  
  143.         observerSvc.removeObserver(this, "profile-after-change");
  144.         observerSvc.removeObserver(this, "profile-before-change");
  145.     },
  146.  
  147.     setUpPrefObservers: function ccm_setUpPrefObservers() {
  148.         var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
  149.                                 .getService(Components.interfaces.nsIPrefBranch2);
  150.         prefBranch.addObserver("calendar.autorefresh.enabled", this, false);
  151.         prefBranch.addObserver("calendar.autorefresh.timeout", this, false);
  152.     },
  153.     
  154.     cleanupPrefObservers: function ccm_cleanupPrefObservers() {
  155.         var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
  156.                                 .getService(Components.interfaces.nsIPrefBranch2);
  157.         prefBranch.removeObserver("calendar.autorefresh.enabled", this);
  158.         prefBranch.removeObserver("calendar.autorefresh.timeout", this);
  159.     },
  160.  
  161.     setUpRefreshTimer: function ccm_setUpRefreshTimer() {
  162.         if (this.mRefreshTimer) {
  163.             this.mRefreshTimer.cancel();
  164.         }
  165.  
  166.         var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
  167.                                 .getService(Components.interfaces.nsIPrefBranch);
  168.  
  169.         var refreshEnabled = false;
  170.         try {
  171.             var refreshEnabled = prefBranch.getBoolPref("calendar.autorefresh.enabled");
  172.         } catch (e) {
  173.         }
  174.  
  175.         // Read and convert the minute-based pref to msecs
  176.         var refreshTimeout = 0;
  177.         try {
  178.             var refreshTimeout = prefBranch.getIntPref("calendar.autorefresh.timeout") * 60000;
  179.         } catch (e) {
  180.         }
  181.  
  182.         if (refreshEnabled && refreshTimeout > 0) {
  183.             this.mRefreshTimer = Components.classes["@mozilla.org/timer;1"]
  184.                                     .createInstance(Components.interfaces.nsITimer);
  185.             this.mRefreshTimer.init(this, refreshTimeout,
  186.                                    Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
  187.         }
  188.     },
  189.  
  190.     setupOfflineObservers: function ccm_setupOfflineObservers() {
  191.         var os = Components.classes["@mozilla.org/observer-service;1"]
  192.                            .getService(Components.interfaces.nsIObserverService);
  193.         os.addObserver(this, "network:offline-status-changed", false);
  194.     },
  195.  
  196.     cleanupOfflineObservers: function ccm_cleanupOfflineObservers() {
  197.         var os = Components.classes["@mozilla.org/observer-service;1"]
  198.                            .getService(Components.interfaces.nsIObserverService);
  199.         os.removeObserver(this, "network:offline-status-changed");
  200.     },
  201.     
  202.     observe: function ccm_observe(aSubject, aTopic, aData) {
  203.         switch (aTopic) {
  204.             case "profile-after-change":
  205.                 this.startup();
  206.                 break;
  207.             case "profile-before-change":
  208.                 this.shutdown();
  209.                 break;
  210.             case "timer-callback":
  211.                 // Refresh all the calendars that can be refreshed.
  212.                 var cals = this.getCalendars({});
  213.                 for each (var cal in cals) {
  214.                     if (cal.canRefresh) {
  215.                         cal.refresh();
  216.                     }
  217.                 }
  218.                 break;
  219.             case "nsPref:changed":
  220.                 if (aData == "calendar.autorefresh.enabled" ||
  221.                     aData == "calendar.autorefresh.timeout") {
  222.                     this.setUpRefreshTimer();
  223.                 }
  224.                 break;
  225.             case "network:offline-status-changed":
  226.                 for each (var calendar in this.mCache) {
  227.                     if (calendar instanceof calCachedCalendar) {
  228.                         calendar.onOfflineStatusChanged(aData == "offline");
  229.                     }
  230.                 }
  231.                 break;
  232.         }
  233.  
  234.     },
  235.  
  236.     DB_SCHEMA_VERSION: 9,
  237.  
  238.     upgradeDB: function (oldVersion) {
  239.         // some common helpers
  240.         function addColumn(db, tableName, colName, colType) {
  241.             db.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
  242.         }
  243.  
  244.         // xxx todo: needs to be resolved with
  245.         //           Bug 377845 - Decouple calendar manager from storage provider
  246.         //
  247.         //           This code always runs before the update code of calStorageCalendar which
  248.         //           needs the old schema version to run.
  249.         //           So this code just updates its schema without updating the version number.
  250.         //
  251.         //           We may run into problems if this code has passed successfully, but the code
  252.         //           in calStorageCalendar hasn't, because on next startup this code will run
  253.         //           into the same section again...
  254.  
  255.         if (oldVersion < 6) {
  256.             dump ("**** Upgrading calCalendarManager schema to 6\n");
  257.  
  258.             this.mDB.beginTransaction();
  259.             try {
  260.                 // Schema changes in v6:
  261.                 //
  262.                 // - Change all STRING columns to TEXT to avoid SQLite's
  263.                 //   "feature" where it will automatically convert strings to
  264.                 //   numbers (ex: 10e4 -> 10000). See bug 333688.
  265.  
  266.                 // Create the new tables.
  267.  
  268.                 try { 
  269.                     this.mDB.executeSimpleSQL("DROP TABLE cal_calendars_v6;" +
  270.                                               "DROP TABLE cal_calendars_prefs_v6;");
  271.                 } catch (e) {
  272.                     // We should get exceptions for trying to drop tables
  273.                     // that don't (shouldn't) exist.
  274.                 }
  275.  
  276.                 this.mDB.executeSimpleSQL("CREATE TABLE cal_calendars_v6 " +
  277.                                           "(id   INTEGER PRIMARY KEY," +
  278.                                           " type TEXT," +
  279.                                           " uri  TEXT);");
  280.  
  281.                 this.mDB.executeSimpleSQL("CREATE TABLE cal_calendars_prefs_v6 " +
  282.                                           "(id       INTEGER PRIMARY KEY," +
  283.                                           " calendar INTEGER," +
  284.                                           " name     TEXT," +
  285.                                           " value    TEXT);");
  286.  
  287.                 // Copy in the data.
  288.                 var calendarCols = ["id", "type", "uri"];
  289.                 var calendarPrefsCols = ["id", "calendar", "name", "value"];
  290.  
  291.                 this.mDB.executeSimpleSQL("INSERT INTO cal_calendars_v6(" + calendarCols.join(",") + ") " +
  292.                                           "     SELECT " + calendarCols.join(",") +
  293.                                           "       FROM cal_calendars");
  294.  
  295.                 this.mDB.executeSimpleSQL("INSERT INTO cal_calendars_prefs_v6(" + calendarPrefsCols.join(",") + ") " +
  296.                                           "     SELECT " + calendarPrefsCols.join(",") +
  297.                                           "       FROM cal_calendars_prefs");
  298.  
  299.  
  300.                 // Delete each old table and rename the new ones to use the
  301.                 // old tables' names.
  302.                 var tableNames = ["cal_calendars", "cal_calendars_prefs"];
  303.  
  304.                 for (var i in tableNames) {
  305.                     this.mDB.executeSimpleSQL("DROP TABLE " + tableNames[i] + ";" +
  306.                                               "ALTER TABLE " + tableNames[i] + "_v6 " + 
  307.                                               "  RENAME TO " + tableNames[i] + ";");
  308.                 }
  309.  
  310.                 this.mDB.commitTransaction();
  311.                 oldVersion = 8;
  312.             } catch (e) {
  313.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  314.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  315.                                              this.mDB.lastErrorString);
  316.                 this.mDB.rollbackTransaction();
  317.                 throw e;
  318.             }
  319.         } 
  320.         
  321.         if (oldVersion < 9) {
  322.             dump ("**** Upgrading calCalendarManager schema to 9\n");
  323.  
  324.             this.mDB.beginTransaction();
  325.             try {
  326.                 // Schema changes in v9:
  327.                 //
  328.                 // - Decouple schema version from storage calendar
  329.                 // Create the new tables.
  330.                 this.mDB.executeSimpleSQL("CREATE TABLE cal_calmgr_schema_version " +
  331.                                           "(version INTEGER);");
  332.                 this.mDB.executeSimpleSQL("INSERT INTO cal_calmgr_schema_version VALUES(" + this.DB_SCHEMA_VERSION + ")");
  333.                 this.mDB.commitTransaction();
  334.                 oldVersion = 9;
  335.             } catch (e) {
  336.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  337.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  338.                                              this.mDB.lastErrorString);
  339.                 this.mDB.rollbackTransaction();
  340.                 throw e;
  341.             }
  342.         }
  343.     },
  344.  
  345.     initDB: function() {
  346.         var dbService = Components.classes[kStorageServiceContractID]
  347.                                   .getService(kStorageServiceIID);
  348.  
  349.         if ("getProfileStorage" in dbService) {
  350.             // 1.8 branch
  351.             this.mDB = dbService.getProfileStorage("profile");
  352.         } else {
  353.             // trunk
  354.             this.mDB = dbService.openSpecialDatabase("profile");
  355.         }
  356.  
  357.         var sqlTables = { cal_calendars: "id INTEGER PRIMARY KEY, type TEXT, uri TEXT",
  358.                           cal_calendars_prefs: "id INTEGER PRIMARY KEY, calendar INTEGER, name TEXT, value TEXT",
  359.                           cal_calmgr_schema_version: "version INTEGER",
  360.                         };
  361.  
  362.         // Should we check the schema version to see if we need to upgrade?
  363.         var checkSchema = true;
  364.         
  365.         // Check if the tables exists
  366.         if (!this.mDB.tableExists("cal_calendars")) {
  367.             // No table. Initialize the DB
  368.             for (table in sqlTables) {
  369.                 this.mDB.createTable(table, sqlTables[table]);
  370.             }
  371.             // Store the schema version
  372.             this.mDB.executeSimpleSQL("INSERT INTO cal_calmgr_schema_version VALUES(" + this.DB_SCHEMA_VERSION + ")");
  373.             checkSchema = false;
  374.         }
  375.  
  376.         if (checkSchema) {
  377.             // Check if we need to upgrade
  378.             var version = this.getSchemaVersion();
  379.             //dump ("*** Calendar schema version is: " + version + "\n");
  380.  
  381.             if (version < this.DB_SCHEMA_VERSION) {
  382.                 this.upgradeDB(version);
  383.             } else if (version > this.DB_SCHEMA_VERSION) {
  384.                 // Schema version is newer than what we know how to deal with.
  385.                 // Alert the user, and quit the app.
  386.                 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  387.                                     .getService(Components.interfaces.nsIStringBundleService);
  388.  
  389.                 var brandSb = sbs.createBundle("chrome://branding/locale/brand.properties");
  390.                 var calSb = sbs.createBundle("chrome://calendar/locale/calendar.properties");
  391.  
  392.                 var hostAppName = brandSb.GetStringFromName("brandShortName");
  393.  
  394.                 // If we're Lightning, we want to include the extension name
  395.                 // in the error message rather than blaming Thunderbird.
  396.                 var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
  397.                                         .getService(Components.interfaces.nsIXULAppInfo);
  398.                 var errorBoxTitle;
  399.                 var errorBoxText;
  400.                 var errorBoxButtonLabel;
  401.                 if (appInfo.ID == SUNBIRD_UID) {
  402.                     errorBoxTitle = calSb.formatStringFromName("tooNewSchemaErrorBoxTitle",
  403.                                                                [hostAppName], 1);
  404.                     errorBoxText = calSb.formatStringFromName("tooNewSchemaErrorBoxTextSunbird",
  405.                                                               [hostAppName], 1);
  406.                     errorBoxButtonLabel = calSb.formatStringFromName("tooNewSchemaButtonQuit",
  407.                                                                      [hostAppName], 1);
  408.                 } else {
  409.                     lightningSb = sbs.createBundle("chrome://lightning/locale/lightning.properties");
  410.                     var calAppName = lightningSb.GetStringFromName("brandShortName");
  411.                     errorBoxTitle = calSb.formatStringFromName("tooNewSchemaErrorBoxTitle",
  412.                                                                [calAppName], 1);
  413.                     errorBoxText = calSb.formatStringFromName("tooNewSchemaErrorBoxTextLightning",
  414.                                                               [calAppName, hostAppName], 2);
  415.                     errorBoxButtonLabel = calSb.formatStringFromName("tooNewSchemaButtonRestart",
  416.                                                                      [hostAppName], 1);
  417.                 }
  418.  
  419.                 var promptSvc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  420.                                           .getService(Components.interfaces.nsIPromptService);
  421.  
  422.                 var errorBoxButtonFlags = promptSvc.BUTTON_POS_0 *
  423.                                           promptSvc.BUTTON_TITLE_IS_STRING +
  424.                                           promptSvc.BUTTON_POS_0_DEFAULT;
  425.  
  426.                 var choice = promptSvc.confirmEx(
  427.                                 null,
  428.                                 errorBoxTitle,
  429.                                 errorBoxText,
  430.                                 errorBoxButtonFlags,
  431.                                 errorBoxButtonLabel,
  432.                                 null, // No second button text
  433.                                 null, // No third button text
  434.                                 null, // No checkbox
  435.                                 {value: false}); // Unnecessary checkbox state
  436.  
  437.                 if (choice == 0) {
  438.                     var startup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
  439.                                             .getService(Components.interfaces.nsIAppStartup);
  440.                     if (appInfo.ID == SUNBIRD_UID) {
  441.                         startup.quit(Components.interfaces.nsIAppStartup.eForceQuit);
  442.                     } else {
  443.                         var em = Components.classes["@mozilla.org/extensions/manager;1"]
  444.                                            .getService(Components.interfaces.nsIExtensionManager);
  445.                         em.disableItem(LIGHTNING_UID);
  446.                         startup.quit(Components.interfaces.nsIAppStartup.eRestart | Components.interfaces.nsIAppStartup.eForceQuit);
  447.                     }
  448.                 }
  449.             }
  450.         }
  451.  
  452.         this.mSelectCalendars = createStatement (
  453.             this.mDB,
  454.             "SELECT oid,* FROM cal_calendars");
  455.  
  456.         this.mRegisterCalendar = createStatement (
  457.             this.mDB,
  458.             "INSERT INTO cal_calendars (type, uri) " +
  459.             "VALUES (:type, :uri)"
  460.             );
  461.  
  462.         this.mUnregisterCalendar = createStatement (
  463.             this.mDB,
  464.             "DELETE FROM cal_calendars WHERE id = :id"
  465.             );
  466.  
  467.         this.mGetPref = createStatement (
  468.             this.mDB,
  469.             "SELECT value FROM cal_calendars_prefs WHERE calendar = :calendar AND name = :name");
  470.  
  471.         this.mDeletePref = createStatement (
  472.             this.mDB,
  473.             "DELETE FROM cal_calendars_prefs WHERE calendar = :calendar AND name = :name");
  474.  
  475.         this.mInsertPref = createStatement (
  476.             this.mDB,
  477.             "INSERT INTO cal_calendars_prefs (calendar, name, value) " +
  478.             "VALUES (:calendar, :name, :value)");
  479.  
  480.         this.mDeletePrefs = createStatement (
  481.             this.mDB,
  482.             "DELETE FROM cal_calendars_prefs WHERE calendar = :calendar");
  483.     },
  484.  
  485.     /** 
  486.      * @return      db schema version
  487.      * @exception   various, depending on error
  488.      */
  489.     getSchemaVersion: function calMgrGetSchemaVersion() {
  490.         var stmt;
  491.         var version = null;
  492.  
  493.         var table;
  494.         if (this.mDB.tableExists("cal_calmgr_schema_version")) {
  495.             table = "cal_calmgr_schema_version";
  496.         } else {
  497.             // Fall back to the old schema table
  498.             table = "cal_calendar_schema_version";
  499.         }
  500.  
  501.         try {
  502.             stmt = createStatement(this.mDB,
  503.                     "SELECT version FROM " + table + " LIMIT 1");
  504.             if (stmt.step()) {
  505.                 version = stmt.row.version;
  506.             }
  507.             stmt.reset();
  508.  
  509.             if (version !== null) {
  510.                 // This is the only place to leave this function gracefully.
  511.                 return version;
  512.             }
  513.         } catch (e) {
  514.             if (stmt) {
  515.                 stmt.reset();
  516.             }
  517.             dump ("++++++++++++ calMgrGetSchemaVersion() error: " +
  518.                   this.mDB.lastErrorString + "\n");
  519.             Components.utils.reportError("Error getting calendar " +
  520.                                          "schema version! DB Error: " + 
  521.                                          this.mDB.lastErrorString);
  522.             throw e;
  523.         }
  524.  
  525.         throw table + " SELECT returned no results";
  526.     },
  527.  
  528.     notifyObservers: function(functionName, args) {
  529.         function notify(obs) {
  530.             try { obs[functionName].apply(obs, args);  }
  531.             catch (e) { }
  532.         }
  533.         this.mObservers.forEach(notify);
  534.     },
  535.  
  536.     /**
  537.      * calICalendarManager interface
  538.      */
  539.     createCalendar: function cmgr_createCalendar(type, uri) {
  540.         try {
  541.             var calendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + type]
  542.                                      .createInstance(Components.interfaces.calICalendar);
  543.             calendar.uri = uri;
  544.             return calendar;
  545.         } catch (ex) {
  546.             ASSERT(false, ex);
  547.             var paramBlock = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  548.                                        .createInstance(Components.interfaces.nsIDialogParamBlock);
  549.             paramBlock.SetNumberStrings(3);
  550.             paramBlock.SetString(0, calGetString("calendar", "unableToCreateProvider", [uri.spec]));
  551.             paramBlock.SetString(1, Components.interfaces.calIErrors.PROVIDER_CREATION_FAILED);
  552.             paramBlock.SetString(2, ex);
  553.             var wWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  554.                                      .getService(Components.interfaces.nsIWindowWatcher);
  555.             wWatcher.openWindow(null,
  556.                                 "chrome://calendar/content/calErrorPrompt.xul",
  557.                                 "_blank",
  558.                                 "chrome,dialog=yes,alwaysRaised=yes",
  559.                                 paramBlock);
  560.             return null;
  561.         }
  562.     },
  563.  
  564.     registerCalendar: function(calendar) {
  565.         // bail if this calendar (or one that looks identical to it) is already registered
  566.         if (calendar.id > 0) {
  567.             dump ("registerCalendar: calendar already registered\n");
  568.             throw Components.results.NS_ERROR_FAILURE;
  569.         }
  570.  
  571.         this.assureCache();
  572.  
  573.         // Add an observer to track readonly-mode triggers
  574.         var newObserver = new calMgrCalendarObserver(calendar, this);
  575.         calendar.addObserver(newObserver);
  576.  
  577.         var pp = this.mRegisterCalendar.params;
  578.         pp.type = calendar.type;
  579.         pp.uri = calendar.uri.spec;
  580.  
  581.         this.mRegisterCalendar.step();
  582.         this.mRegisterCalendar.reset();
  583.         
  584.         calendar.id = this.mDB.lastInsertRowID;
  585.         
  586.         //dump("adding [" + this.mDB.lastInsertRowID + "]\n");
  587.         //this.mCache[this.mDB.lastInsertRowID] = calendar;
  588.         this.mCache[calendar.id] = calendar;
  589.         this.mCalObservers[calendar.id] = newObserver;
  590.  
  591.         // Set up statistics
  592.         if (calendar.getProperty("requiresNetwork") !== false) {
  593.             this.mNetworkCalendarCount++;
  594.         }
  595.         if (calendar.readOnly) {
  596.             this.mReadonlyCalendarCount++;
  597.         }
  598.         this.mCalendarCount++;
  599.  
  600.         this.notifyObservers("onCalendarRegistered", [calendar]);
  601.     },
  602.  
  603.     unregisterCalendar: function(calendar) {
  604.         this.notifyObservers("onCalendarUnregistering", [calendar]);
  605.  
  606.         // calendar may be a calICalendar wrapper:
  607.         if (calendar.wrappedJSObject instanceof calCachedCalendar) {
  608.             calendar.wrappedJSObject.onCalendarUnregistering();
  609.         }
  610.  
  611.         var calendarID = calendar.id;
  612.  
  613.         calendar.removeObserver(this.mCalObservers[calendarID]);
  614.  
  615.         var pp = this.mUnregisterCalendar.params;
  616.         pp.id = calendarID;
  617.         this.mUnregisterCalendar.step();
  618.         this.mUnregisterCalendar.reset();
  619.  
  620.         // delete prefs for the calendar too
  621.         pp = this.mDeletePrefs.params;
  622.         pp.calendar = calendarID;
  623.         this.mDeletePrefs.step();
  624.         this.mDeletePrefs.reset();
  625.  
  626.         if (this.mCache) {
  627.             delete this.mCache[calendarID];
  628.         }
  629.  
  630.         if (calendar.readOnly) {
  631.             this.mReadonlyCalendarCount--;
  632.         }
  633.  
  634.         if (calendar.getProperty("requiresNetwork") !== false) {
  635.             this.mNetworkCalendarCount--;
  636.         }
  637.         this.mCalendarCount--;
  638.     },
  639.  
  640.     deleteCalendar: function(calendar) {
  641.         /* check to see if calendar is unregistered first... */
  642.         /* delete the calendar for good */
  643.         if (this.mCache && (calendar.id in this.mCache)) {
  644.             throw "Can't delete a registered calendar";
  645.         }
  646.         this.notifyObservers("onCalendarDeleting", [calendar]);
  647.  
  648.         // XXX This is a workaround for bug 351499. We should remove it once
  649.         // we sort out the whole "delete" vs. "unsubscribe" UI thing.
  650.         //
  651.         // We only want to delete the contents of calendars from local
  652.         // providers (storage and memory). Otherwise we may nuke someone's
  653.         // calendar stored on a server when all they really wanted to do was
  654.         // unsubscribe.
  655.         if (calendar instanceof Components.interfaces.calICalendarProvider &&
  656.            (calendar.type == "storage" || calendar.type == "memory")) {
  657.             try {
  658.                 calendar.deleteCalendar(calendar, null);
  659.             } catch (e) {
  660.                 Components.utils.reportError("error purging calendar: " + e);
  661.             }
  662.         }
  663.     },
  664.  
  665.     getCalendars: function cmgr_getCalendars(count) {
  666.         this.assureCache();
  667.         var calendars = [];
  668.         for each (var cal in this.mCache) {
  669.             calendars.push(cal);
  670.         }
  671.         count.value = calendars.length;
  672.         return calendars;
  673.     },
  674.  
  675.     assureCache: function cmgr_assureCache() {
  676.         if (!this.mCache) {
  677.             this.mCache = {};
  678.             this.mCalObservers = {};
  679.  
  680.             var stmt = this.mSelectCalendars;
  681.             stmt.reset();
  682.             var newCalendarData = [];
  683.             while (stmt.step()) {
  684.                 newCalendarData.push( { id: stmt.row.id, type: stmt.row.type, uri: stmt.row.uri } );
  685.             }
  686.             stmt.reset();
  687.  
  688.             for each (var caldata in newCalendarData) {
  689.                 try {
  690.                     var cal = this.createCalendar(caldata.type, makeURL(caldata.uri));
  691.                     cal.id = caldata.id;
  692.                     if ((cal.getProperty("cache.supported") !== false) && cal.getProperty("cache.enabled")) {
  693.                         cal = new calCachedCalendar(cal);
  694.                     }
  695.                     var newObserver = new calMgrCalendarObserver(cal, this);
  696.                     cal.addObserver(newObserver);
  697.                     this.mCalObservers[caldata.id] = newObserver;
  698.  
  699.                     this.mCache[caldata.id] = cal;
  700.  
  701.                     // Set up statistics
  702.                     if (cal.getProperty("requiresNetwork") !== false) {
  703.                         this.mNetworkCalendarCount++;
  704.                     }
  705.                     if (cal.readOnly) {
  706.                         this.mReadonlyCalendarCount++;
  707.                     }
  708.                     this.mCalendarCount++;
  709.                 } catch (exc) {
  710.                     Components.utils.reportError(
  711.                         "Can't create calendar for " + caldata.id +
  712.                         " (" + caldata.type + ", " + caldata.uri + "): " + exc);
  713.                 }
  714.             }
  715.         }
  716.     },
  717.  
  718.     getCalendarPref_: function(calendar, name) {
  719.         ASSERT(calendar, "Invalid Calendar");
  720.         ASSERT(calendar.id !== null, "Calendar id needs to be an integer");
  721.         ASSERT(name && name.length > 0, "Pref Name must be non-empty");
  722.  
  723.         // pref names must be lower case
  724.         name = name.toLowerCase();
  725.  
  726.         var stmt = this.mGetPref;
  727.         stmt.reset();
  728.         var pp = stmt.params;
  729.         pp.calendar = calendar.id;
  730.         pp.name = name;
  731.  
  732.         var value = null;
  733.         if (stmt.step()) {
  734.             value = stmt.row.value;
  735.         }
  736.         stmt.reset();
  737.         return value;
  738.     },
  739.  
  740.     setCalendarPref_: function(calendar, name, value) {
  741.         ASSERT(calendar, "Invalid Calendar");
  742.         ASSERT(calendar.id !== null, "Calendar id needs to be an integer");
  743.         ASSERT(name && name.length > 0, "Pref Name must be non-empty");
  744.  
  745.         // pref names must be lower case
  746.         name = name.toLowerCase();
  747.        
  748.         var calendarID = calendar.id;
  749.  
  750.         this.mDB.beginTransaction();
  751.  
  752.         var pp = this.mDeletePref.params;
  753.         pp.calendar = calendarID;
  754.         pp.name = name;
  755.         this.mDeletePref.step();
  756.         this.mDeletePref.reset();
  757.  
  758.         pp = this.mInsertPref.params;
  759.         pp.calendar = calendarID;
  760.         pp.name = name;
  761.         pp.value = value;
  762.         this.mInsertPref.step();
  763.         this.mInsertPref.reset();
  764.  
  765.         this.mDB.commitTransaction();
  766.     },
  767.  
  768.     deleteCalendarPref_: function(calendar, name) {
  769.         ASSERT(calendar, "Invalid Calendar");
  770.         ASSERT(calendar.id !== null, "Calendar id needs to be an integer");
  771.         ASSERT(name && name.length > 0, "Pref Name must be non-empty");
  772.  
  773.         // pref names must be lower case
  774.         name = name.toLowerCase();
  775.  
  776.         var calendarID = calendar.id;
  777.  
  778.         var pp = this.mDeletePref.params;
  779.         pp.calendar = calendarID;
  780.         pp.name = name;
  781.         this.mDeletePref.step();
  782.         this.mDeletePref.reset();
  783.     },
  784.     
  785.     mObservers: Array(),
  786.     addObserver: function(aObserver) {
  787.         if (this.mObservers.indexOf(aObserver) != -1)
  788.             return;
  789.  
  790.         this.mObservers.push(aObserver);
  791.     },
  792.  
  793.     removeObserver: function(aObserver) {
  794.         function notThis(v) {
  795.             return v != aObserver;
  796.         }
  797.         
  798.         this.mObservers = this.mObservers.filter(notThis);
  799.     }
  800. };
  801.  
  802. function equalMessage(msg1, msg2) {
  803.     if (msg1.GetString(0) == msg2.GetString(0) &&
  804.         msg1.GetString(1) == msg2.GetString(1) &&
  805.         msg1.GetString(2) == msg2.GetString(2)) {
  806.         return true;
  807.     }
  808.     return false;
  809. }
  810.  
  811. function calMgrCalendarObserver(calendar, calMgr) {
  812.     this.calendar = calendar;
  813.     // We compare this to determine if the state actually changed.
  814.     this.storedReadOnly = calendar.readOnly;
  815.     this.announcedMessages = [];
  816.     this.calMgr = calMgr;
  817. }
  818.  
  819. calMgrCalendarObserver.prototype = {
  820.     calendar: null,
  821.     storedReadOnly: null,
  822.     calMgr: null,
  823.  
  824.     QueryInterface: function mBL_QueryInterface(aIID) {
  825.         ensureIID(
  826.             [ Components.interfaces.nsIWindowMediatorListener,
  827.               Components.interfaces.calIObserver,
  828.               Components.interfaces.nsISupports], aIID);
  829.         return this;
  830.     },
  831.  
  832.     // nsIWindowMediatorListener:
  833.     onCloseWindow: function(aXulWindow) {
  834.         try {
  835.             var aDOMWindow = aXulWindow.docShell
  836.                 .QueryInterface(
  837.                     Components.interfaces.nsIInterfaceRequestor)
  838.                 .getInterface(
  839.                     Components.interfaces.nsIDOMWindow);
  840.             var args = aDOMWindow.arguments[0]
  841.                 .QueryInterface(
  842.                     Components.interfaces.nsIDialogParamBlock);
  843.  
  844.             // remove the message that has been shown from
  845.             // the list of all announced messages.
  846.             this.announcedMessages = this.announcedMessages.filter(
  847.                 function(announcedMessage) {
  848.                     return !equalMessage(announcedMessage, args);
  849.                 });
  850.  
  851.             // if the list is now empty we can safely remove the listener
  852.             if (!this.announcedMessages.length) {
  853.                 var windowMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  854.                                      .getService(Components.interfaces.nsIWindowMediator);
  855.                 windowMediator.removeListener(this);
  856.             }
  857.  
  858.         } catch (e) {
  859.             Components.utils.reportError(e);
  860.         }
  861.     },
  862.  
  863.     onOpenWindow: function(aXulWindow) {},
  864.     onWindowTitleChange: function(aXulWindow,aNewTitle) {},
  865.  
  866.     // calIObserver:
  867.     onStartBatch: function() {},
  868.     onEndBatch: function() {},
  869.     onLoad: function(calendar) {},
  870.     onAddItem: function(aItem) {},
  871.     onModifyItem: function(aNewItem, aOldItem) {},
  872.     onDeleteItem: function(aDeletedItem) {},
  873.     onError: function(aErrNo, aMessage) {
  874.         this.announceError(aErrNo, aMessage);
  875.     },
  876.  
  877.     onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
  878.         switch (aName) {
  879.             case "requiresNetwork":
  880.                 this.calMgr.mNetworkCalendarCount += (aValue ? 1 : -1);
  881.                 break;
  882.             case "readOnly":
  883.                 this.calMgr.mReadonlyCalendarCount += (aValue ? 1 : -1);
  884.                 break;
  885.             case "cache.enabled":
  886.                 if (aCalendar.wrappedJSObject instanceof calCachedCalendar) {
  887.                     // any attempt to switch this flag will reset the cached calendar;
  888.                     // could be useful for users in case the cache may be corrupted.
  889.                     aCalendar.wrappedJSObject.setupCachedCalendar();
  890.                 }
  891.                 break;
  892.         }
  893.     },
  894.  
  895.     onPropertyDeleting: function(aCalendar, aName) {
  896.         this.onPropertyChanged(aCalendar, aName, false, true);
  897.     },
  898.  
  899.     // Error announcer specific functions
  900.  
  901.     announceError: function(aErrNo, aMessage) {
  902.         var paramBlock = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  903.                                    .createInstance(Components.interfaces.nsIDialogParamBlock);
  904.         var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  905.                             .getService(Components.interfaces.nsIStringBundleService);
  906.         var props = sbs.createBundle("chrome://calendar/locale/calendar.properties");
  907.         var errMsg;
  908.         paramBlock.SetNumberStrings(3);
  909.         if (!this.storedReadOnly && this.calendar.readOnly) {
  910.             // Major errors change the calendar to readOnly
  911.             errMsg = props.formatStringFromName("readOnlyMode", [this.calendar.name], 1);
  912.         } else if (!this.storedReadOnly && !this.calendar.readOnly) {
  913.             // Minor errors don't, but still tell the user something went wrong
  914.             errMsg = props.formatStringFromName("minorError", [this.calendar.name], 1);
  915.         } else {
  916.             // The calendar was already in readOnly mode, but still tell the user
  917.             errMsg = props.formatStringFromName("stillReadOnlyError", [this.calendar.name], 1);
  918.         }
  919.  
  920.         // When possible, change the error number into its name, to
  921.         // make it slightly more readable.
  922.         var errCode = "0x"+aErrNo.toString(16);
  923.         const calIErrors = Components.interfaces.calIErrors;
  924.         // Check if it is worth enumerating all the error codes.
  925.         if (aErrNo & calIErrors.ERROR_BASE) {
  926.             for (var err in calIErrors) {
  927.                 if (calIErrors[err] == aErrNo) {
  928.                     errCode = err;
  929.                 }
  930.             }
  931.         }
  932.  
  933.         var message;
  934.         switch (aErrNo) {
  935.             case calIErrors.CAL_UTF8_DECODING_FAILED:
  936.                 message = props.GetStringFromName("utf8DecodeError");
  937.                 break;
  938.             case calIErrors.ICS_MALFORMEDDATA:
  939.                 message = props.GetStringFromName("icsMalformedError");
  940.                 break;
  941.             default:
  942.                 message = aMessage
  943.         }
  944.  
  945.         paramBlock.SetString(0, errMsg);
  946.         paramBlock.SetString(1, errCode);
  947.         paramBlock.SetString(2, message);
  948.  
  949.         // silently don't do anything if this message already has
  950.         // been announed without being acknowledged.
  951.         if (this.announcedMessages.some(
  952.             function(element, index, array) {
  953.                 return equalMessage(paramBlock, element);
  954.             })) {
  955.             return;
  956.         }
  957.  
  958.         // if no message has been announced, i.e. no window has been
  959.         // raised, we need to install the appropriate listener now.
  960.         if (!this.announcedMessages.length) {
  961.             Components.classes["@mozilla.org/appshell/window-mediator;1"]
  962.                       .getService(Components.interfaces.nsIWindowMediator)
  963.                       .addListener(this);
  964.         }
  965.  
  966.         // this message hasn't been announced recently, remember the
  967.         // details of the message for future reference.
  968.         this.announcedMessages.push(paramBlock);
  969.  
  970.         this.storedReadOnly = this.calendar.readOnly;
  971.  
  972.         var wWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  973.                                  .getService(Components.interfaces.nsIWindowWatcher);
  974.         wWatcher.openWindow(null,
  975.                             "chrome://calendar/content/calErrorPrompt.xul",
  976.                             "_blank",
  977.                             "chrome,dialog=yes,alwaysRaised=yes",
  978.                             paramBlock);
  979.     }
  980. }
  981.